import { isSupported, apply } from "dialog-toggle-events-polyfill/fn";
if (!isSupported()) {
    apply();
}

import './scrollend.js';
import 'toolcool-color-picker';
import "./z-webcom.css"
import "./c_config.js"
import "./c_text.js"
import "./c_each.js"
import "./c_cond.js"
import "./c_switch.js"
import "./c_truncate.js"
import "./c_datepicker.js"
import "./c_checkbox.js"
import "./c_timepicker.js"
import "./c_datetimelocalpicker.js"
import "./c_optionpicker.js"
import "./c_inputpopup.js"
import "./c_dialog.js";
import "./c_message.js";
import "./c_panels.js"
import "./c_zfcinput.js";
import "./c_imageslider.js"
import {initToastr} from "./toastr.js";
import {bindRootEle} from "./core.js"
import {_getZCondVars, COMMON_FUN, runExpr} from "./jexpr.js"
import {CondsMap, deepGet, deepSet} from "./frm.js";
import {getPathKeys} from "./ext.js";
import {parseServerTemplate} from "./core_tpl.js";
import {initWheelEvent} from "./core_event.js";

let boolAttrNames = [
    "allowfullscreen",
    "async",
    "autofocus",
    "autoplay",
    "checked",
    "controls",
    "default",
    "defer",
    "disabled",
    "formnovalidate",
    "inert",
    "ismap",
    "itemscope",
    "loop",
    "multiple",
    "muted",
    "nomodule",
    "novalidate",
    "open",
    "playsinline",
    "readonly",
    "required",
    "reversed",
    "selected",
    "shadowrootclonable",
    "shadowrootdelegatesfocus",
    "shadowrootserializable"
]
let setAttr = function(element, attrname, value) {
    if (boolAttrNames.includes(attrname) || attrname.startsWith('bool__')) {
        // console.log('setAttr', attrname, typeof value);
        if (value === false) {
            element.removeAttribute(attrname);
        }
        else {
            element.setAttribute(attrname, 'true');
        }
    }
    else {
        element.setAttribute(attrname, value);
    }
}

export let SetDataOptions = {
    TYPE_PUSH: {
        type: 'push'
    },
    TYPE_DEL: {
        type: 'dele'
    }
}


/**
 * @namespace typedefs
 */

/**
 * @exports  SimpleApp
 * @typedef SimpleApp
 * @prop  {{type: string}} TYPE_PUSH
 * @prop {Function} getData
 * @prop {Function} setData
 */

let CUR_APP_INDEX = 0;

/**
 *
 * @param options {{}}
 * @returns {SimpleApp}
 */
export function buildSimpleApp(options = {}) {


    let globalDomMap;
    let gloExprFun;
    let gloZEachArr;
    if (!options.sub) {
        CUR_APP_INDEX = CUR_APP_INDEX + 1;
        options.level = 0;
        gloExprFun = {
            ...COMMON_FUN,
            ...options.FUN
        }
        options.ins = {
            gloExprFun,
            domMap: new CondsMap(),
            gloZEachArr:  []
        }

        if (typeof window !== 'undefined') {
            window['_gloZEachArr' + CUR_APP_INDEX] = options.ins.gloZEachArr;
            window['_globalDomMap' + CUR_APP_INDEX] = options.ins.domMap;
            window['_gloExprFun' + CUR_APP_INDEX] = options.ins.gloExprFun;
        }
    }
    globalDomMap =  options.ins.domMap;
    gloZEachArr =  options.ins.gloZEachArr;

    /**
     *
     * @param path {string}
     */
    function getLikeArrayPath(path = '') {
        let shengyupath = path.trim();
        let reg = /([\w_\d]*)\.([\d]+)/g
        let res = path.matchAll(reg);
        let arrres = [...res];
        arrres.forEach((v) => {
            shengyupath = shengyupath.replace(v[0], '');
            if (shengyupath[0] === '.') {
                shengyupath =shengyupath.slice(1, shengyupath.length );
            }
        })

        let level = arrres.length - 1;
        let last = arrres.at(-1)
        let find = gloZEachArr.find(c => {
            if (c.level === level && c.name === last[1]) {
                return true
            }
            return false
        })
        // console.log(arrres, shengyupath, level, last, find);
        if (find) {

            let index = parseInt(last[2])

            if (shengyupath !== '') {
                return {
                    path: find.node.eachBlockValueName + '.' + shengyupath,
                    isArray: true,
                    isSubItem: false,
                    index: index
                }
            }

            if (shengyupath === '') {
                let truepatharr = [...arrres].slice(0, arrres.length - 1).map((v, index) => {
                    let find = gloZEachArr.find(c => {
                        if (c.level === index && c.name === v[1]) {
                            return true
                        }
                        return false
                    })
                    if (find) {
                        return  find.node.eachBlockValueName
                    }
                    return ''
                });

                let s = truepatharr.join('.') + (truepatharr.length > 0 ? '.' : '')
                let path =  s + arrres.at(-1)[1]
                console.log('webcom truepatharr', truepatharr, path)
                return {
                    path,
                    isSubItem: true,
                    isArray: true,
                    index: index
                }
            }
        }
        return {
            path,
            isSubItem: false,
            isArray: false,
            index: undefined
        }
    }

    globalThis._getLikeArrayPath = getLikeArrayPath;

    let rootEle = null;
    let ret = {};
    let ctx =  {};
    let originData = options?.data() ?? {};
    ctx.domMap = new CondsMap();
    ctx.bindMap = new CondsMap();

    /**
     *
     * @param node {Node}
     * @param nodeoption {{}}
     */
    function registerNode(node, nodeoption = {}){
        if (node.getConds) {
            let conds = node.getConds();
            conds.forEach(cond => {
                let c = {
                    node,
                    reload(opt = {}) {
                        node?.notify(opt)
                    }
                }
                function checkNomeEqual(oldArr = []) {
                    // let keys = oldArr.map(elem => elem.key)
                    // return keys.includes(cond)
                    // let nodes = oldArr.map(elem => elem.node)
                    if (oldArr.some(v => v?.node === node)) {
                        console.log('webcom checkNomeEqual', cond, node)
                        return true
                    }

                    return false
                }
                // ctx.domMap.setNotSame(cond, c, checkNomeEqual);
                globalDomMap.setNotSame(cond, c, checkNomeEqual)

                // let kbj= {
                //     key: cond,
                // }
                // function checkIsSame(oldArr = []) {
                //     let keys = oldArr.map(elem => elem.key)
                //     return keys.includes(cond)
                // }
                // ctx.bindMap.setNotSame(node, kbj, checkIsSame)
                // gloDomBinds.setNotSame(node, kbj, checkIsSame)
                if (node.nodeName === 'Z-EACH') {
                    eachSetKeys.add(cond)
                    let varname = cond.split('.')
                    gloZEachArr.push({
                        node,
                        cond,
                        level: options.level,
                        name: varname.at(-1)
                    })
                }
            });
            console.log('webcom registerNode', node, ctx)
        }
    }

    function unRegisterNode(node) {
        if (node.getConds) {
            let conds = node.getConds();
            conds.forEach(cond => {
                // ctx.domMap.delete(cond);
                // globalDomMap.delete(cond)
                let arr = globalDomMap.get(cond)
                let newarr = arr.filter(v => {
                    if (v.node === node) {
                        return false
                    }
                    return  true
                });
                globalDomMap.delete(cond)
                globalDomMap.set(cond, newarr);

                // ctx.bindMap.delete(cond)
                // gloDomBinds.delete(node)

                if (node.nodeName === 'Z-EACH') {
                    eachSetKeys.delete(cond)
                }
            });
            console.log('webcom unRegisterNode', conds, node)
        }
    }

    ctx.exportCtx = function() {
        let createSubApp = function (subData = {}) {
            return buildSimpleApp({
                sub: true,
                level: options.level + 1,
                data() {
                    return {
                       ...originData,
                       ...subData,
                    }
                },
                ins: options.ins,
                methods: {
                    ...options.methods
                }
            });
        }
        function init(shadowRoot) {
            // initGlobalEvents(shadowRoot)
        }
        return {
            getOptions() {
              return options
            },
            getMethods() {
              return options.methods
            },
            getDomMap() {
                return ctx.domMap;
            },
            get domMap() {
                return ctx.domMap;
            },
            register(node, opt = {}) {
                registerNode(node, opt);
            },
            unRegister(node) {
                unRegisterNode(node);
            },
            initShadow: init,
            createSubApp,
            getData() {
                return originData;
            }
        }
    }

    let eachSetKeys = new Set();
    ctx.onTravel = function(attrname, attrvalue, node) {
        let controlNames = ['Z-EACH', 'Z-COND'];
        // console.log(node.nodeName)
        if (controlNames.includes(node.nodeName)) {
            node.$ctx = ctx.exportCtx()

            if (node?.mounted) {
                node?.mounted()
            }
            console.log('webcom onTravel', node, node?.mounted)
            // registerNode(node);
        }
        if (attrname.startsWith("_:")) {
            let newAttrName = attrname.slice(2, attrname.length)
            function renderstatic() {
                // console.log('webcom render', newAttrName, node.oldvalue, node);
                let result = runExpr(attrvalue, {
                    ...originData
                }, options.ins.gloExprFun);

                setAttr(node, newAttrName, result);
                requestAnimationFrame(() => {
                    node.removeAttribute(attrname)
                })
            }
            renderstatic()
        }
        if (attrname.startsWith(":")) {
            // console.log('webcom attr', attrname, attrvalue, node);
            let newAttrName = attrname.slice(1, attrname.length)
            function render(newvalue) {
                console.log('webcom render', newAttrName, newvalue, node.oldvalue, node);
                // 值不变 就不改了
                if (newvalue === node.oldvalue && typeof node.oldvalue !== 'undefined') {

                    return;
                }

                let result = runExpr(attrvalue, {
                    ...originData
                }, options.ins.gloExprFun);

                node.oldvalue = result;
                setAttr(node, newAttrName, result);
                requestAnimationFrame(() => {
                    node.removeAttribute(attrname)
                })
            }
            render()

            let conds = new Set()
            _getZCondVars(attrvalue, conds);
            // console.log('webcom sssssssss', attrvalue, conds)
            let condArr = [attrvalue]
            if (conds.size > 0) {
                condArr = [attrvalue, ...conds]
            }

            let c = {
                node,
                reload({newvalue} = {}) {
                    render(newvalue)
                }
            }
            function checkNomeEqual(oldArr = []) {
                // let keys = oldArr.map(elem => elem.key)
                // return keys.includes(cond)
                // let nodes = oldArr.map(elem => elem.node)
                if (oldArr.some(v => v?.node === node)) {
                    console.log('webcom checkNomeEqual', node)
                    return true
                }

                return false
            }
            condArr.forEach(condItem => {
                // ctx.domMap.set(condItem, c);
                globalDomMap.setNotSame(condItem, c, checkNomeEqual)
            })


            // let kbj= {
            //     key: newAttrName,
            // }
            // ctx.bindMap.set(node, kbj)
            // gloDomBinds.set(node, kbj)
        }
    }

    /**
     *
     * @type {bindRootRet | null}
     */
    ctx.bindRootRet = null

    let last_dom_value = {};
    ret._last_dom_value = last_dom_value;
    function udpateDomMap(path = '', newvalue, {index} = {}) {
        let dompath = path;
        // let arrayKeys = [...eachSetKeys]
        let truepathobj = getLikeArrayPath(path);
        if (typeof index !== 'undefined') {
            truepathobj.index = index;
        }

        console.log('webcom setData', path, truepathobj)
        if (truepathobj) {
            dompath = truepathobj.path
        }
        last_dom_value[dompath] = newvalue
        if (globalDomMap.has(dompath)) {
            let deps = globalDomMap.get(dompath);
            console.log('webcom setData', dompath, deps);
            if (Array.isArray(deps)) {
                deps.forEach((dep) => {
                    if (dep  && dep.reload) {
                        if (truepathobj) {
                            let keys = getPathKeys(path);

                            dep.reload({
                                keys,
                                newvalue,
                                key: truepathobj.key,
                                isSubItem: truepathobj.isSubItem,
                                index: truepathobj?.index
                            })
                        }
                        else {
                            dep.reload({
                                newvalue
                            })
                        }
                    }
                })
            }
        }
    }
    ret.updateContent = function (path) {
        // console.log('ssssssssssssss', path, ctx.domMap);
        udpateDomMap(path)
    }
    ret.mount = function(element) {
        rootEle = element;
        rootEle.$ctx = ctx.exportCtx();
        ctx.bindRootRet = bindRootEle(rootEle, options?.methods, ctx);
    }
    let setHandlers = {
        'push': function (data = {}, path = '') {
            let newpath = path;
            let targetArr = deepGet(data, path);
            if (Array.isArray(targetArr)) {
                newpath = newpath + '.' + targetArr.length;
            }
            return {path: newpath}
        },
        'dele': function (data, path = '') {
            let newpath = path;

            let pathArr =  path.split('.');
            newpath = pathArr.slice(0, pathArr.length - 1).join('.');
            let targetArr = deepGet(data, newpath);
            console.log('webcom dele', targetArr, newpath);
            let value = undefined
            if (Array.isArray(targetArr)) {
                let index = parseInt(pathArr.at(-1));
                // console.log('webcom dele', index);
                if (!Number.isNaN(index)) {
                     targetArr.splice(index, 1);
                    value = targetArr;
                    // console.log('webcom dele', value, newpath);
                }
            }
            return {path: newpath, value}
        }
    }
    ret.TYPE_PUSH = {
        type: 'push',
    }
    let LAST_VALUE = {
    }
    ret._LAST_VALUE = LAST_VALUE;
    ret.setData = async function(path = '', value, {type = ''} = {}) {
        queueMicrotask(() => {
            LAST_VALUE[path] = value;

            if (type === 'dele') {
                let res = setHandlers[type](originData, path)
               if (typeof res.value !== 'undefined') {
                   deepSet(originData, res.path, res.value);
                   console.log('webcom dele', res.path, res.value)

                   udpateDomMap(path, value, {index: -1});
               }
            }
            else {

                let res = {}
                if (setHandlers[type]) {
                    res = setHandlers[type](originData, path)
                    path = res.path
                }
                console.log('webcom', type, path, originData)
                deepSet(originData, path, value);

                udpateDomMap(path, value);
            }

        })
    }
    ret.getData = function (path = '') {
        if (path === '') {
            return originData;
        }
        return deepGet(originData, path);
    }
    ret.destory = function () {
        if (ctx.bindRootRet) {
            ctx.bindRootRet.unbindEvents()
        }

        setTimeout(() => {
            options.ins.gloZEachArr = null
            options.ins.domMap = null
            options.ins.gloExprFun = null;
        })

        window['_gloZEachArr' + CUR_APP_INDEX] = null;
        window['_globalDomMap' + CUR_APP_INDEX] = null;
        window['_gloExprFun' + CUR_APP_INDEX] = null;
    }
    let _setSubAppBeforeContent = null
    ret.setSubAppBefore = function(tpldom) {
        _setSubAppBeforeContent = tpldom.content.cloneNode(true)
    }

    ret.exportCtx = ctx.exportCtx
    return ret;
}

/**
 *
 * @param shadowRoot
 * @returns {{destory: Function}}
 */
export function initRootEvents(shadowRoot) {
    function mousemove(e) {
        // console.log(e.target)
        if (e.target.classList && e.target.classList.contains("z-button")) {
            let button = e.target;
            let x = e.offsetX;
            let y = e.offsetY;
            button.style.setProperty('--mouse-x', x + "px");
            button.style.setProperty('--mouse-y', y + "px");
        }
    }
    shadowRoot.addEventListener('mousemove', mousemove);
    return {
        destory: function () {
            shadowRoot.removeEventListener('mousemove', mousemove);
        }
    }
}

/**
 *
 * @param globalDoc
 * @returns {{destory: Function}}
 */
export function initGlobal(globalDoc = document) {
    let ret = initRootEvents(globalDoc)
    initToastr();
    return ret;
}

export let version = '0.0.1';

function extendFun() {
    // COMMON_FUN
}

class BaseElement extends EventTarget {
    $elm = null;
    constructor() {
        super();
    }
    mounted() {
    }
    unmounted() {
    }
    attrChanged(name, oldValue, newValue) {
    }
}

class BaseElementRep {
    elementDefs = new Map()
    define(name, cls) {
        this.elementDefs.set(name, cls)
    }
    get(name) {
        return this.elementDefs.get(name);
    }
}

let baseElementRep = new BaseElementRep()

function setHtml(dom, htmlString = '', {rep = baseElementRep, meta = {}} = {}) {

    let parser = new DOMParser();
    let doc = parser.parseFromString(`<div>${htmlString}</div>`, "text/html")
    let res = {}
    res.nodes = [...doc.body.childNodes[0].childNodes];
    res.flats = [];

    function buildNode(targetNode, config = { attributes: true, childList: true,   attributeOldValue: true, }, {onChildList} = {}) {
        let cls = rep.get(targetNode.tagName.toLowerCase());
        let cus = null;
        if (cls) {
            cus = new cls();
            cus.$elm = targetNode;
            res.flats.push({
                cls: cus,
                node: targetNode
            });
            targetNode.$cus = cls;
        }

        const callback = (mutationList, observer) => {
            for (const mutation of mutationList) {
                if (mutation.type === "childList") {
                    // console.log("setHtml A child node has been added or removed.", mutation, targetNode);
                    if (onChildList) {
                        onChildList(mutation, targetNode);
                    }
               } else if (mutation.type === "attributes") {
                    // console.log(`setHtml The ${mutation.attributeName} attribute was modified.`, mutation, targetNode);

                    if (cus) {
                        let newval = targetNode.getAttribute(mutation.attributeName);
                        cus?.attrChanged(mutation.attributeName, mutation.oldValue, newval)
                    }
                }
            }
        };

        if (cus) {
            cus?.mounted()
        }

        targetNode.$observer = new MutationObserver(callback);
        targetNode.$observer.observe(targetNode, config);
    }

    function attachNode(item) {
        if (item.tagName && item.tagName.toLowerCase().includes('m-')) {
            console.log('setHtml attachNode', item, res.flats);
            buildNode(item)
        }
    }

    let opt = {
    }
    function travel(arr, option) {
        arr.forEach((item, index) => {
            // attachNode(item)
            if (item.children) {
                travel([...item.children], option);
            }
        })
    }

    travel(res.nodes, opt);
    buildNode(dom,
        { attributes: true, childList: true,   subtree: true },
        {onChildList: function (mutation)
            {
                if (mutation.removedNodes.length > 0) {
                    [...mutation.removedNodes].forEach(removedNode => {
                        // console.log('setHtml removedNode', removedNode, res.flats);
                        let index = res.flats.findIndex(v => v.node === removedNode)
                        if (index > -1) {
                            let deletedItem = res.flats[index]
                            // console.log('setHtml ssssssssssssssssss', deletedItem);

                            if (deletedItem.cls?.unmounted) {
                                deletedItem.cls?.unmounted()
                            }

                            res.flats.splice(index, 1);

                            removedNode.$observer.disconnect()
                        }
                    })
                }
                if (mutation.addedNodes.length > 0) {
                    [...mutation.addedNodes].forEach(addedNode => {
                         // console.log('setHtml addedNodes', addedNode, res.flats);
                        attachNode(addedNode)
                    })
                }
            }
        }
    )

    dom.append(...res.nodes);
}


function parseVTemplate(str, parseConfig) {
    return parseServerTemplate(str, parseConfig)
}


/**
 *
 * @param rootEle {HTMLElement}
 * @param tplEle {HTMLTemplateElement | string}
 * @param options {{keepTag: Boolean}}
 */
let setTemplate = function (rootEle, tplEle, options = {keepTag: true}) {
    let vtemplateRes = {}
    let html = tplEle
    if (tplEle instanceof HTMLElement) {
        html = tplEle.innerHTML
    }
    vtemplateRes = parseVTemplate(
        html,
        {keepTag: options.keepTag},
    );
    console.log('setHtml', vtemplateRes)

    setHtml(rootEle,
        vtemplateRes.html,
        {meta: vtemplateRes.meta}
    );
}

if (typeof HTMLElement !== 'undefined') {
    /**
     *
     * @param tplELe {HTMLTemplateElement | string}
     */
    HTMLElement.prototype.zSetTemplate = function (tplELe) {
        setTemplate(
            this,
            tplELe
        )
    }
}

function createHostDiv(id) {
    let hiddendiv = document.getElementById(id)
    if (!hiddendiv) {
        let cacheSet = new Set();
        hiddendiv = document.createElement("div");
        hiddendiv.setAttribute("id", id);
        hiddendiv.setAttribute("hidden", "true");

        hiddendiv.add =function (ele) {
            if (!cacheSet.has(ele)) {
                cacheSet.add(ele);
                hiddendiv.append(ele)
            }
        }
        document.body.append(hiddendiv);
    }
    return hiddendiv
}

globalThis.ZWebcom = {
    version,
    setHtml,
    BaseElement,
    initWheelEvent,
    createHostDiv,
    setTemplate,
    baseElementRep,
    buildSimpleApp,
    parseVTemplate,
    extendFun,
    SetDataOptions,
    initGlobal,
}